package org.rainwalk.bill.controller;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.formula.functions.T;
import org.rainwalk.bill.api.exception.BizException;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatter;
import org.rainwalk.bill.api.formatter.annotation.EnumFormatters;
import org.rainwalk.bill.api.formatter.annotation.MoneyFormatter;
import org.rainwalk.bill.api.formatter.annotation.TimeFormatter;
import org.rainwalk.bill.api.interceptor.AuthInterceptor;
import org.rainwalk.bill.api.log.ApiLog;
import org.rainwalk.bill.api.model.Response;
import org.rainwalk.bill.api.model.ResponseCode;
import org.rainwalk.bill.api.model.SessionUser;
import org.rainwalk.bill.entity.Bill;
import org.rainwalk.bill.entity.Category;
import org.rainwalk.bill.model.dto.BillAddDTO;
import org.rainwalk.bill.model.dto.BillImportDTO;
import org.rainwalk.bill.model.dto.BillQueryDTO;
import org.rainwalk.bill.model.dto.BillUpdateDTO;
import org.rainwalk.bill.model.enums.BillChargeTypeEnum;
import org.rainwalk.bill.model.enums.BillStateEnum;
import org.rainwalk.bill.model.enums.BillTypeEnum;
import org.rainwalk.bill.model.enums.DescValueEnum;
import org.rainwalk.bill.model.vo.AddBillVO;
import org.rainwalk.bill.model.vo.BillListVO;
import org.rainwalk.bill.model.vo.BillStatisticsVO;
import org.rainwalk.bill.model.vo.BillVO;
import org.rainwalk.bill.service.IBillService;
import org.rainwalk.bill.service.ICategoryService;
import org.rainwalk.bill.util.IdGenerator;
import org.rainwalk.bill.util.MoneyUtil;
import org.rainwalk.bill.util.TimeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 账单 前端控制器
 * </p>
 *
 * @author 趁雨行
 * @since 2021-02-18
 */
@Api(tags = "账单模块")
@RestController
@RequestMapping("/bill")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class BillController {

    private final IBillService billService;

    private final ICategoryService categoryService;

    @ApiOperation("导入微信账单")
    @ApiLog("导入微信账单")
    @PostMapping("/wechat")
    public Response importWechatBill(@Validated BillImportDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        if (!"csv".equals(FileUtil.extName(dto.getFile().getOriginalFilename()))) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "请上传csv格式文件");
        }
        AddBillVO addBillVO = billService.importWechatBill(dto.getFile(), dto.getIgnoreDuplicate(), sessionUser.getId());
        return Response.success(addBillVO);
    }

    @ApiOperation("导入支付宝账单")
    @ApiLog("导入支付宝账单")
    @PostMapping("/alipay")
    public Response importAlipayBill(@Validated BillImportDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        if (!"csv".equals(FileUtil.extName(dto.getFile().getOriginalFilename()))) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "请上传csv格式文件");
        }
        AddBillVO addBillVO = billService.importAlipayBill(dto.getFile(), dto.getIgnoreDuplicate(), sessionUser.getId());
        return Response.success(addBillVO);
    }

    @ApiOperation("更新账单")
    @ApiImplicitParam(value = "账单id", name = "id", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @ApiLog("更新账单")
    @PutMapping("/{id}")
    public Response updateBill(@PathVariable String id, @RequestBody @Valid BillUpdateDTO dto) {
        Bill bill = this.billService.getById(id);
        if (bill == null) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "账单不存在");
        }
        BeanUtil.copyProperties(dto, bill, CopyOptions.create());
        checkBillCategory(bill);
        bill.setCreateTime(TimeUtil.toTimestamp(dto.getCreateTime()));
        bill.setCreateTimePoint(TimeUtil.toTimePoint(dto.getCreateTime()));
        bill.setAmount(MoneyUtil.yuan2fen(dto.getAmount()));
        bill.setServiceFee(MoneyUtil.yuan2fen(dto.getServiceFee()));
        bill.setRefundAmount(MoneyUtil.yuan2fen(dto.getRefundAmount()));
        bill.setUserRemark(null);

        this.billService.updateBill(bill);
        return Response.success();
    }

    @ApiOperation("更新账单类别")
    @ApiImplicitParam(value = "账单id", name = "id", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @ApiLog("更新账单类别")
    @PutMapping("/{id}/category")
    public Response updateBillCategory(@PathVariable String id, @RequestBody BillUpdateDTO dto) {
        Bill bill = this.billService.getById(id);
        if (bill == null) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "账单不存在");
        }

        bill = new Bill();
        bill.setId(id);
        bill.setCategoryId(dto.getCategoryId());
        checkBillCategory(bill);
        this.billService.updateCategory(bill);
        return Response.success();
    }

    @ApiOperation("更新账单备注")
    @ApiImplicitParam(value = "账单id", name = "id", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @ApiLog("更新账单备注")
    @PutMapping("/{id}/user-remark")
    public Response updateBillUserRemark(@PathVariable String id, @RequestBody BillUpdateDTO dto) {
        Bill bill = this.billService.getById(id);
        if (bill == null) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "账单不存在");
        }

        bill = new Bill();
        bill.setId(id);
        bill.setUserRemark(dto.getUserRemark());
        this.billService.updateUserRemark(bill);
        return Response.success();
    }



    @ApiOperation("手动新增账单")
    @ApiLog("手动新增账单")
    @PostMapping
    public Response addBill(@RequestBody @Validated BillAddDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        Bill bill = new Bill();
        BeanUtil.copyProperties(dto, bill);
        checkBillCategory(bill);
        if (StrUtil.isNotBlank(dto.getCreateTime())) {
            bill.setCreateTime(TimeUtil.toTimestamp(dto.getCreateTime()));
            bill.setCreateTimePoint(TimeUtil.toTimePoint(dto.getCreateTime()));
        }
        bill.setAmount(MoneyUtil.yuan2fen(dto.getAmount()));
        bill.setServiceFee(MoneyUtil.yuan2fen(dto.getServiceFee()));
        bill.setRefundAmount(MoneyUtil.yuan2fen(dto.getRefundAmount()));
        bill.setId(IdGenerator.generateId());
        bill.setBatchNo(IdGenerator.generateId());
        bill.setUserId(sessionUser.getId());
        bill.setBillType(BillTypeEnum.CUSTOMER.getValue());
        this.billService.save(bill);
        return Response.success();
    }

    @ApiOperation("查询账单")
    @GetMapping
    @MoneyFormatter(fields = {"amount", "serviceFee", "refundAmount"})
    @TimeFormatter(fields = {"createTime"})
    @EnumFormatters({
            @EnumFormatter(field = "chargeType", enumClazz = BillChargeTypeEnum.class),
            @EnumFormatter(field = "state", enumClazz = BillStateEnum.class),
            @EnumFormatter(field = "billType", enumClazz = BillTypeEnum.class)
    })
    public Response query(@Validated BillQueryDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        Page<BillListVO> page = this.billService.queryPage(dto.toPo(sessionUser.getId()));
        return Response.success(page);
    }

    @ApiOperation("查询账单数据统计")
    @GetMapping("/statistics")
    @MoneyFormatter(fields = {"expenditure", "income","serviceFee", "refundAmount"})
    public Response queryStatistics(@Validated BillQueryDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        BillStatisticsVO vo = this.billService.queryStatistics(dto.toPo(sessionUser.getId()));
        return Response.success(BeanUtil.beanToMap(vo));
    }

    @ApiOperation("通过ID查询账单")
    @ApiImplicitParam(value = "账单id", name = "id", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @GetMapping("/{id}")
    @MoneyFormatter(fields = {"amount", "serviceFee", "refundAmount"})
    @TimeFormatter(fields = {"createTime"})
    @EnumFormatters({
            @EnumFormatter(field = "chargeType", enumClazz = BillChargeTypeEnum.class),
            @EnumFormatter(field = "state", enumClazz = BillStateEnum.class),
            @EnumFormatter(field = "billType", enumClazz = BillTypeEnum.class)
    })
    public Response queryById(@PathVariable String id, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        Bill bill = this.billService.getByIdAndUser(id, sessionUser.getId());
        if (bill == null) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "找不到相关账单");
        }
        BillVO vo = new BillVO();
        BeanUtil.copyProperties(bill, vo);
        if (StrUtil.isNotBlank(vo.getCategoryId())) {
            Category category = this.categoryService.getById(vo.getCategoryId());
            if (category != null) {
                vo.setCategory(category.getCategoryName());
            }
        }
        return Response.success(BeanUtil.beanToMap(vo));
    }

    @ApiOperation("删除账单")
    @ApiImplicitParam(value = "账单id", name = "id", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @ApiLog("删除账单")
    @DeleteMapping("/{id}")
    public Response delete(@PathVariable String id, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        Bill bill = this.billService.getByIdAndUser(id, sessionUser.getId());
        if (bill == null) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "找不到相关账单");
        }
        this.billService.removeById(id);
        return Response.success();
    }

    @ApiOperation("撤回账单导入")
    @ApiLog("撤回账单导入")
    @ApiImplicitParam(value = "批次号", name = "batchNo", dataTypeClass = String.class, paramType = "path", required = true, example = "12354545")
    @DeleteMapping("/withdraw/{batchNo}")
    public Response withdraw(@PathVariable String batchNo, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser) {
        List<Bill> bills = this.billService.getByBatchNoAndUser(batchNo, sessionUser.getId());
        if (bills.size() == 0) {
            return Response.fail(ResponseCode.PARAMS_VERITY_FAIL, "找不到相关账单");
        }
        this.billService.removeByIds(bills.stream().map(Bill::getId).collect(Collectors.toList()));
        return Response.success(String.format("撤回成功，共撤回%d条账单", bills.size()));
    }

    @ApiOperation("导出账单")
    @GetMapping("/export")
    public void export(@Validated BillQueryDTO dto, @SessionAttribute(AuthInterceptor.USER_SESSION) SessionUser sessionUser, HttpServletResponse response) {
        List<Map<String, Object>> rows = this.billService.queryListFormatter(dto.toPo(sessionUser.getId()));

        List<Object> columns = rows.stream().map(row -> {
            HashMap<String, Object> column = MapUtil.newHashMap(16, true);
            column.put("交易单号", row.get("transactionNo"));
            column.put("商户单号", row.get("merchantOrderNo"));
            column.put("交易时间", row.get("createTimeFormatter"));
            column.put("交易类型", row.get("transactionType"));
            column.put("交易对方", row.get("otherParty"));
            column.put("商品名称", row.get("goodsName"));
            column.put("交易金额", row.get("amountFormatter"));
            column.put("计费类型", row.get("chargeTypeFormatter"));
            column.put("交易状态描述", row.get("stateDesc"));
            column.put("交易状态", row.get("stateFormatter"));
            column.put("服务费", row.get("serviceFeeFormatter"));
            column.put("成功退款", row.get("refundAmountFormatter"));
            column.put("备注", row.get("remark"));
            column.put("账单类型", row.get("billTypeFormatter"));
            column.put("用户备注", row.get("userRemark"));
            column.put("用户消费类别", row.get("categoryName"));
            return column;
        }).collect(Collectors.toList());
        ExcelWriter writer = ExcelUtil.getWriter();
        writer.write(columns, true);
        String filename = "bill_" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
        response.setContentType("application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + filename + ".xls");
        try (OutputStream os = response.getOutputStream()){
            writer.flush(os);
        } catch (IOException e) {
            throw new BizException(ResponseCode.SERVER_ERROR, "导出错误");
        } finally {
            writer.close();
        }
    }

    /**
     * 校验账单分类
     *
     * @param bill
     */
    private void checkBillCategory(Bill bill) {
        if (StrUtil.isNotBlank(bill.getCategoryId())) {
            if ("unclassified".equals(bill.getCategoryId())) {
                bill.setCategoryId(null);
            } else {
                if (this.categoryService.getById(bill.getCategoryId()) == null) {
                    throw new BizException(ResponseCode.PARAMS_VERITY_FAIL, "分类有误");
                }
            }
        }
    }

}
